/*
 * contrib/pg_trgm/trgm_gin.c
 */
#include "postgres.h"

#include "access/gin.h"
#include "access/stratnum.h"
#include "fmgr.h"
#include "trgm.h"

PG_FUNCTION_INFO_V1(gin_extract_trgm);
PG_FUNCTION_INFO_V1(gin_extract_value_trgm);
PG_FUNCTION_INFO_V1(gin_extract_query_trgm);
PG_FUNCTION_INFO_V1(gin_trgm_consistent);
PG_FUNCTION_INFO_V1(gin_trgm_triconsistent);

/*
 * 此函数仅在目录中存在预9.1版本的GIN运算符
 * 类定义时才可以调用（可能是因
 * 升级而导致）。请处理。
 */
Datum gin_extract_trgm(PG_FUNCTION_ARGS)
{
	if (PG_NARGS() == 3)
		return gin_extract_value_trgm(fcinfo);
	if (PG_NARGS() == 7)
		return gin_extract_query_trgm(fcinfo);
	elog(ERROR, "unexpected number of arguments to gin_extract_trgm");
	PG_RETURN_NULL();
}

Datum gin_extract_value_trgm(PG_FUNCTION_ARGS)
{
	text	   *fc_val = (text *) PG_GETARG_TEXT_PP(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	Datum	   *fc_entries = NULL;
	TRGM	   *fc_trg;
	int32		fc_trglen;

	*fc_nentries = 0;

	fc_trg = generate_trgm(VARDATA_ANY(fc_val), VARSIZE_ANY_EXHDR(fc_val));
	fc_trglen = ARRNELEM(fc_trg);

	if (fc_trglen > 0)
	{
		trgm	   *fc_ptr;
		int32		fc_i;

		*fc_nentries = fc_trglen;
		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_trglen);

		fc_ptr = GETARR(fc_trg);
		for (fc_i = 0; fc_i < fc_trglen; fc_i++)
		{
			int32		fc_item = trgm2int(fc_ptr);

			fc_entries[fc_i] = Int32GetDatum(fc_item);
			fc_ptr++;
		}
	}

	PG_RETURN_POINTER(fc_entries);
}

Datum gin_extract_query_trgm(PG_FUNCTION_ARGS)
{
	text	   *fc_val = (text *) PG_GETARG_TEXT_PP(0);
	int32	   *fc_nentries = (int32 *) PG_GETARG_POINTER(1);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(2);

	/* bool   **pmatch = (bool **) PG_GETARG_POINTER(3); */
	Pointer   **fc_extra_data = (Pointer **) PG_GETARG_POINTER(4);

	/* bool   **nullFlags = (bool **) PG_GETARG_POINTER(5); */
	int32	   *fc_searchMode = (int32 *) PG_GETARG_POINTER(6);
	Datum	   *fc_entries = NULL;
	TRGM	   *fc_trg;
	int32		fc_trglen;
	trgm	   *fc_ptr;
	TrgmPackedGraph *fc_graph;
	int32		fc_i;

	switch (fc_strategy)
	{
		case SimilarityStrategyNumber:
		case WordSimilarityStrategyNumber:
		case StrictWordSimilarityStrategyNumber:
		case EqualStrategyNumber:
			fc_trg = generate_trgm(VARDATA_ANY(fc_val), VARSIZE_ANY_EXHDR(fc_val));
			break;
		case ILikeStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case LikeStrategyNumber:

			/*
			 * 对于通配符搜索，我们提取所有的三元组，
			 * 每个潜在匹配字符串必须包含这些三元组。
			 */
			fc_trg = generate_wildcard_trgm(VARDATA_ANY(fc_val),
										 VARSIZE_ANY_EXHDR(fc_val));
			break;
		case RegExpICaseStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case RegExpStrategyNumber:
			fc_trg = createTrgmNFA(fc_val, PG_GET_COLLATION(),
								&fc_graph, CurrentMemoryContext);
			if (fc_trg && ARRNELEM(fc_trg) > 0)
			{
				/*
				 * 正则表达式处理成功：将NFA-like图存储为
				 * extra_data。GIN API需要一个nentries的
				 * 指针数组，但我们只是将相同的值放入每个元素。
				 */
				fc_trglen = ARRNELEM(fc_trg);
				*fc_extra_data = (Pointer *) palloc(sizeof(Pointer) * fc_trglen);
				for (fc_i = 0; fc_i < fc_trglen; fc_i++)
					(*fc_extra_data)[fc_i] = (Pointer) fc_graph;
			}
			else
			{
				/* 没有结果：必须执行完整的索引扫描。 */
				*fc_nentries = 0;
				*fc_searchMode = GIN_SEARCH_MODE_ALL;
				PG_RETURN_POINTER(fc_entries);
			}
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
			fc_trg = NULL;			/* 保持编译器安静 */
			break;
	}

	fc_trglen = ARRNELEM(fc_trg);
	*fc_nentries = fc_trglen;

	if (fc_trglen > 0)
	{
		fc_entries = (Datum *) palloc(sizeof(Datum) * fc_trglen);
		fc_ptr = GETARR(fc_trg);
		for (fc_i = 0; fc_i < fc_trglen; fc_i++)
		{
			int32		fc_item = trgm2int(fc_ptr);

			fc_entries[fc_i] = Int32GetDatum(fc_item);
			fc_ptr++;
		}
	}

	/*
	 * 如果没有提取到三元组，那么我们必须扫描整个索引。
	 */
	if (fc_trglen == 0)
		*fc_searchMode = GIN_SEARCH_MODE_ALL;

	PG_RETURN_POINTER(fc_entries);
}

Datum gin_trgm_consistent(PG_FUNCTION_ARGS)
{
	bool	   *fc_check = (bool *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* text    *query = PG_GETARG_TEXT_PP(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	bool	   *fc_recheck = (bool *) PG_GETARG_POINTER(5);
	bool		fc_res;
	int32		fc_i,
				fc_ntrue;
	double		fc_nlimit;

	/* 此函数处理的所有情况都是不准确的 */
	*fc_recheck = true;

	switch (fc_strategy)
	{
		case SimilarityStrategyNumber:
		case WordSimilarityStrategyNumber:
		case StrictWordSimilarityStrategyNumber:
			fc_nlimit = index_strategy_get_limit(fc_strategy);

			/* 计算匹配次数 */
			fc_ntrue = 0;
			for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
			{
				if (fc_check[fc_i])
					fc_ntrue++;
			}

			/*--------------------
			 * 如果定义了DIVUNION，则相似度公式为：
			 * c / (len1 + len2 - c)
			 * 其中c是共同三元组的数量，在
			 * 此代码中作为ntrue存在。这里我们不知道len2的值，
			 * 但我们可以假设c（ntrue）是len2的下界，
			 * 因此，相似度的上界为：
			 * c / (len1 + c - c)  => c / len1
			 * 如果未定义DIVUNION，则相似度公式为：
			 * c / max(len1, len2)
			 * 同样，c（ntrue）是len2的下界，
			 * 但是由于定义的关系，c <= len1，
			 * 因此，相似度的上界就是c / len1。
			 * 因此，无论DIVUNION如何，上界公式都是相同的。
			 */
			fc_res = (fc_nkeys == 0) ? false :
				(((((float4) fc_ntrue) / ((float4) fc_nkeys))) >= fc_nlimit);
			break;
		case ILikeStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case LikeStrategyNumber:
		case EqualStrategyNumber:
			/* 检查所有提取的三元组是否存在。 */
			fc_res = true;
			for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
			{
				if (!fc_check[fc_i])
				{
					fc_res = false;
					break;
				}
			}
			break;
		case RegExpICaseStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case RegExpStrategyNumber:
			if (fc_nkeys < 1)
			{
				/* 正则表达式处理没有结果：执行完整的索引扫描 */
				fc_res = true;
			}
			else
				fc_res = trigramsMatchGraph((TrgmPackedGraph *) fc_extra_data[0],
										 fc_check);
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
			fc_res = false;		/* 保持编译器安静 */
			break;
	}

	PG_RETURN_BOOL(fc_res);
}

/*
 * 在所有情况下，GIN_TRUE至少与
 * GIN_MAYBE一样有利于包含。如果没有更好的选项可用，
 * 则简单地将GIN_MAYBE视为GIN_TRUE，并应用与二进制
 * 一致函数相同的测试。
 */
Datum gin_trgm_triconsistent(PG_FUNCTION_ARGS)
{
	GinTernaryValue *fc_check = (GinTernaryValue *) PG_GETARG_POINTER(0);
	StrategyNumber fc_strategy = PG_GETARG_UINT16(1);

	/* text    *query = PG_GETARG_TEXT_PP(2); */
	int32		fc_nkeys = PG_GETARG_INT32(3);
	Pointer    *fc_extra_data = (Pointer *) PG_GETARG_POINTER(4);
	GinTernaryValue fc_res = GIN_MAYBE;
	int32		fc_i,
				fc_ntrue;
	bool	   *fc_boolcheck;
	double		fc_nlimit;

	switch (fc_strategy)
	{
		case SimilarityStrategyNumber:
		case WordSimilarityStrategyNumber:
		case StrictWordSimilarityStrategyNumber:
			fc_nlimit = index_strategy_get_limit(fc_strategy);

			/* 计算匹配次数 */
			fc_ntrue = 0;
			for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
			{
				if (fc_check[fc_i] != GIN_FALSE)
					fc_ntrue++;
			}

			/*
			 * 请参见gin_trgm_consistent()中的注释关于*上界
			 * 公式
			 */
			fc_res = (fc_nkeys == 0)
				? GIN_FALSE : (((((float4) fc_ntrue) / ((float4) fc_nkeys)) >= fc_nlimit)
							   ? GIN_MAYBE : GIN_FALSE);
			break;
		case ILikeStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case LikeStrategyNumber:
		case EqualStrategyNumber:
			/* 检查所有提取的三元组是否存在。 */
			fc_res = GIN_MAYBE;
			for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
			{
				if (fc_check[fc_i] == GIN_FALSE)
				{
					fc_res = GIN_FALSE;
					break;
				}
			}
			break;
		case RegExpICaseStrategyNumber:
#ifndef IGNORECASE
			elog(ERROR, "cannot handle ~* with case-sensitive trigrams");
#endif
			/* FALL THRU */
		case RegExpStrategyNumber:
			if (fc_nkeys < 1)
			{
				/* 正则表达式处理没有结果：执行完整的索引扫描 */
				fc_res = GIN_MAYBE;
			}
			else
			{
				/*
				 * 由于trigramsMatchGraph实现了一个单调布尔
				 * 函数，将所有GIN_MAYBE键提升到GIN_TRUE将
				 * 得到一个保守的结果。
				 */
				fc_boolcheck = (bool *) palloc(sizeof(bool) * fc_nkeys);
				for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
					fc_boolcheck[fc_i] = (fc_check[fc_i] != GIN_FALSE);
				if (!trigramsMatchGraph((TrgmPackedGraph *) fc_extra_data[0],
										fc_boolcheck))
					fc_res = GIN_FALSE;
				pfree(fc_boolcheck);
			}
			break;
		default:
			elog(ERROR, "unrecognized strategy number: %d", fc_strategy);
			fc_res = GIN_FALSE;	/* 保持编译器安静 */
			break;
	}

	/* 此函数处理的所有情况都是不准确的 */
	Assert(fc_res != GIN_TRUE);
	PG_RETURN_GIN_TERNARY_VALUE(fc_res);
}
